﻿
using System.Text;
using Draco.DataStructure;

namespace Draco.Algorithms
{
    public class D_GeoHash
    {
        /* geohash长度对应表
         |----------------------------------------------------------|
         | hash  |   边长       |   误差         |     9格误差      |
         |-------|--------------|----------------|------------------|
         |  1    |   500m       |   ±250m       |     ±750m       |
         |  2    |   250m       |   ±125m       |     ±375m       |
         |  3    |   125m       |   ±62.5m      |     ±187.5m     |
         |  4    |   62.5m      |   ±31.25m     |     ±93.75m     |
         |  5    |   31.25m     |   ±15.625m    |     ±46.875m    |
         |  6    |   15.625m    |   ±7.8125m    |     ±23.4375m   |
         |  7    |   7.8125m    |   ±3.90625m   |     ±11.71875m  |
         |  8    |   3.90625m   |   ±1.953125m  |     ±5.859375m  |
         |----------------------------------------------------------|
         */

        private D_LocationBean location;

        private int hashLength = 8;//XY化为geohash长度  
        private int xLength = 8;//X转化为二进制长度 
        private int yLength = 8;//Y转化为二进制长度  
        

        private double minX;//每格X的单位大小  
        private double minY;//每格Y的单位大小  

        private static readonly char[] CHARS ={ 'a', 'b', 'c', 'd'};  //每两位对应一个hash

        public D_GeoHash(double x, double y)
        {
            location = new D_LocationBean(x,y);
            SetMinXY();
        }

        public int GetHashLength()
        {
            return hashLength;
        }

        //设置XY转化为geohash长度
        public bool SetHashLenght(int length)
        {
            if (length < 1)
            {
                return false;
            }

            hashLength = length;
            xLength = length;
            yLength = length;
            SetMinXY();
            return true;
        }

        //获取XY的base4字符串
        public string GetGeoHashBase4()
        {
            return GetGeoHashBase4(location.X, location.Y);
        }

        //求所在坐标点及周围点组成的九个
        public D_LinkList<string> GetGeoHashBase4For9()
        {
            double leftX = location.X - minX;
            double rightX = location.X + minX;
            double upY = location.Y - minY;
            double downY = location.Y + minY;
            D_LinkList<string> base4For9 = new D_LinkList<string>();
            //左侧从上到下 3个  
            string leftUp = GetGeoHashBase4(leftX, upY);
            if (!(leftUp == null || "".Equals(leftUp)))
            {
                base4For9.Add(leftUp);
            }
            string leftMid = GetGeoHashBase4(leftX, location.Y);
            if (!(leftMid == null || "".Equals(leftMid)))
            {
                base4For9.Add(leftMid);
            }
            string leftDown = GetGeoHashBase4(leftX, downY);
            if (!(leftDown == null || "".Equals(leftDown)))
            {
                base4For9.Add(leftDown);
            }
            //中间从上到下 3个  
            string midUp = GetGeoHashBase4(location.X, upY);
            if (!(midUp == null || "".Equals(midUp)))
            {
                base4For9.Add(midUp);
            }
            string midMid = GetGeoHashBase4(location.X, location.Y);
            if (!(midMid == null || "".Equals(midMid)))
            {
                base4For9.Add(midMid);
            }
            string midDown = GetGeoHashBase4(location.X, downY);
            if (!(midDown == null || "".Equals(midDown)))
            {
                base4For9.Add(midDown);
            }
            //右侧从上到下 3个  
            string rightUp = GetGeoHashBase4(rightX, upY);
            if (!(rightUp == null || "".Equals(rightUp)))
            {
                base4For9.Add(rightUp);
            }
            string rightMid = GetGeoHashBase4(rightX, location.Y);
            if (!(rightMid == null || "".Equals(rightMid)))
            {
                base4For9.Add(rightMid);
            }
            string rightDown = GetGeoHashBase4(rightX, downY);
            if (!(rightDown == null || "".Equals(rightDown)))
            {
                base4For9.Add(rightDown);
            }
            return base4For9;
        }

        public static string GetGeoHash(double x,double y)
        {
            D_GeoHash geo = new D_GeoHash(x,y);
            string hash = geo.GetGeoHashBase4();
            geo = null;
            return hash;
        }

        //设置XY的最小单位
        private void SetMinXY()
        {
            minX = D_LocationBean.MAX_X - D_LocationBean.MIN_X;
            for (int i = 0; i < xLength; i++)
            {
                minX /= 2.0;
            }
            minY = D_LocationBean.MAX_Y - D_LocationBean.MIN_Y;
            for (int i = 0; i < yLength; i++)
            {
                minY /= 2.0;
            }
        }

        //将数字转化为geohash二进制字符串
        private bool[] GetHashArray(double value, double min, double max, int length)
        {
            if (value < min || value > max)
            {
                return null;
            }
            if (length < 1)
            {
                return null;
            }

            bool[] result = new bool[length];
            for (int i = 0; i < length; i++)
            {
                double mid = (min + max) / 2.0;
                if (value > mid)
                {
                    result[i] = true;
                    min = mid;
                }
                else
                {
                    result[i] = false;
                    max = mid;
                }
            }
            return result;
        }

        //合并XY二进制
        private bool[] Merge(bool[] xArray, bool[] yArray)
        {
            if (xArray == null || yArray == null)
            {
                return null;
            }
            bool[] result = new bool[xArray.Length+ yArray.Length];
            for (int i = 0; i < result.Length; i++)
            {
                result[i] = false;
            }
            for (int i = 0; i < yArray.Length; i++)
            {
                result[2 * i] = yArray[i];
            }
            for (int i = 0; i < xArray.Length; i++)
            {
                result[2 * i + 1] = xArray[i];
            }
            return result;
        }

        //获取XY的base4字符串
        private string GetGeoHashBase4(double x, double y)
        {
            bool[] bools = GetGeoBinary(x, y);
            if (bools == null)
            {
                return null;
            }

            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < bools.Length; i = i + 2)
            {
                bool[] base4 = new bool[2];
                for (int j = 0; j < 2; j++)
                {
                    base4[j] = bools[i + j];
                }
                char c = GetBase4Char(base4);
                if (' ' == c)
                {
                    return null;
                }
                sb.Append(c);
            }
            return sb.ToString();
        }

        //获取坐标的geo二进制字符串
        private bool[] GetGeoBinary(double x, double y)
        {
            bool[] xArray = GetHashArray(x, D_LocationBean.MIN_X, D_LocationBean.MAX_X, xLength);
            bool[] yArray = GetHashArray(y, D_LocationBean.MIN_Y, D_LocationBean.MAX_Y, yLength);
            return Merge(xArray, yArray);
        }

        //将两位二进制转化为base4
        private char GetBase4Char(bool[] base4)
        {
            if (base4 == null || base4.Length != 2)
            {
                return ' ';
            }
            int num = 0;
            foreach (bool b in base4)
            {
                num <<= 1;
                if (b)
                {
                    num += 1;
                }
            }
            return CHARS[num % CHARS.Length];
        }



    }


}
